#pragma once

#include <iostream>
#include <vector>
#include <pthread.h>
#include "MyMutex.hpp"
#include "MySem.hpp"

#define CAPACITY 5

template<typename T>
class ring_queue
{
public:
    ring_queue(int capacity = CAPACITY)
        :_capacity(capacity)
        ,_rq(_capacity)
        ,_blank_sem(_capacity)//初始全为空格
        ,_productor_index(0)
        ,_full_sem(0)
        ,_consumer_index(0)
    {}

    void push(const T& data)//生产者调用
    {
        //_productor_mutex.lock();//a: 先在门口等着, 一个一个进去之后再买票

        //1.[预定]申请一个空格, 预定失败(没空格了)则阻塞 直到"有票"
        _blank_sem.P();
        //此时队列里一定有空格

        _productor_mutex.lock();//b: 先买票, 再一个个进来 -- [效率高], 而且sem是原子的, 无影响

        //2.真正将data插入空格
        _rq[_productor_index++] = data;
        _productor_index %= _capacity;//保持环形队列性质

        _productor_mutex.unlock();//b
        
        //3.有数据的格子+1, 并唤醒在_full_sem下等待的消费端线程
        _full_sem.V();

        //_productor_mutex.unlock();//a
    }

    T pop()//消费者调用
    {
        //1.[预定]申请一个有数据的格子, 预定失败(全是空格)则阻塞 直到"有票"
        _full_sem.P();
        //此时队列里一定有数据格

        _consumer_mutex.lock();
        //2.真正拿走数据
        T ret = _rq[_consumer_index++];
        _consumer_index %= _capacity;

        _consumer_mutex.unlock();

        //3.空格+1, 并唤醒在_blank_sem下等待的生产端线程
        _blank_sem.V();

        return ret;
    }

    ~ring_queue()
    {}

private:
    int _capacity;
    std::vector<T> _rq;//和阻塞队列不同的是: rq并非为视为一整个临界资源, 而是把每一位视为一个子资源, 生产端和消费端可以并发的访问不同位置的子资源
                       //生消访问同一位置 <--> 为空或为满时: 利用sem维持同步机制
                       //不为空或不为满时, 生消访问不同位置


    //生产端
    sem _blank_sem;//记录环形队列中空格总数
    int _productor_index;//记录此时生产端线程访问的位置
    mutex _productor_mutex;//维护多生产端进程间的互斥关系

    //消费端
    sem _full_sem;//记录环形队列中有数据的格子总数
    int _consumer_index;//记录此时消费端线程访问的位置
    mutex _consumer_mutex;////维护多消费端进程间的互斥关系
};